ποΈGitΠ―ΡΠ°ποΈ
docs/BUILD_LOGIC_CONVENTIONS_GUIDE.md 20d934459a489ffab53d9dfd88e841ea3df58e6a (20d93445) Text, 10.29 KB
Build-Logic Convention Patterns & Guidelines
Quick reference for maintaining and extending the build-logic convention system.
Core Principles
1. DRY (Don't Repeat Yourself): Extract common configuration into functions
2. Clarity Over Cleverness: Explicit intent in T383838build.gradle.kts files matters
3. Single Responsibility: Each convention plugin has one clear purpose
4. Test-Driven: Configuration changes must pass T383838spotlessCheck, T383838detekt, and tests
Convention Plugin Architecture
T282828
build-logic/
βββ convention/
β βββ src/main/kotlin/
β β βββ KmpFeatureConventionPlugin.kt # KMP feature modules (composes library + compose + koin + common deps)
β β βββ KmpLibraryConventionPlugin.kt # KMP modules: core libraries
β β βββ KmpLibraryComposeConventionPlugin.kt # KMP Compose Multiplatform setup
β β βββ KmpJvmAndroidConventionPlugin.kt # Opt-in jvmAndroidMain hierarchy for Android + desktop JVM
β β βββ AndroidApplicationConventionPlugin.kt # Main app
β β βββ AndroidLibraryConventionPlugin.kt # Android-only libraries
β β βββ AndroidApplicationComposeConventionPlugin.kt
β β βββ AndroidLibraryComposeConventionPlugin.kt
β β βββ org/meshtastic/buildlogic/
β β β βββ KotlinAndroid.kt # Base Kotlin/Android config
β β β βββ AndroidCompose.kt # Compose setup
β β β βββ FlavorResolution.kt # Flavor configuration
β β β βββ MeshtasticFlavor.kt # Flavor definitions
β β β βββ Detekt.kt # Static analysis
β β β βββ Spotless.kt # Code formatting
β β β βββ ... (other config modules)
How to Add a New Convention
Example: Adding a new test framework dependency
Current Pattern (GOOD β
):
If all KMP modules need a dependency, add it to T383838KotlinAndroid.kt::configureKmpTestDependencies():
T282828
Tff7b72internal Tff7b72fun Te6edf3ProjectTb4b4b4.Td2a8ffconfigureKmpTestDependenciesTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3extensionsTb4b4b4.Te6edf3configureTff7b72<Te6edf3KotlinMultiplatformExtensionTff7b72> Tb4b4b4{
Te6edf3sourceSetsTb4b4b4.Te6edf3apply Tb4b4b4{
Tff7b72val Te6edf3commonTest Tff7b72= Te6edf3findByNameTb4b4b4(Ta5d6ff"Ta5d6ffcommonTestTa5d6ff"Tb4b4b4) Tff7b72?: Tff7b72returnTf0883e@apply
Te6edf3commonTestTb4b4b4.Te6edf3dependencies Tb4b4b4{
Te6edf3implementationTb4b4b4(Te6edf3kotlinTb4b4b4(Ta5d6ff"Ta5d6fftestTa5d6ff"Tb4b4b4)Tb4b4b4)
T8b949e// NEW: Add here once, applies to all ~15 KMP modules
Te6edf3implementationTb4b4b4(Te6edf3libsTb4b4b4.Te6edf3libraryTb4b4b4(Ta5d6ff"Ta5d6ffnew-test-frameworkTa5d6ff"Tb4b4b4)Tb4b4b4)
Tb4b4b4}
T8b949e// ... androidHostTest setup
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Result: All 15 feature and core modules automatically get the dependency β
Example: Adding shared T383838jvmAndroidMain code to a KMP module
Current Pattern (GOOD β
):
If a KMP module needs Java/JVM APIs shared between Android and desktop JVM, apply the opt-in convention plugin instead of manually creating source sets and T383838dependsOn(...) edges:
T282828
Te6edf3plugins Tb4b4b4{
Te6edf3aliasTb4b4b4(Te6edf3libsTb4b4b4.Te6edf3pluginsTb4b4b4.Te6edf3meshtasticTb4b4b4.Te6edf3kmpTb4b4b4.Te6edf3libraryTb4b4b4)
Te6edf3idTb4b4b4(Ta5d6ff"Ta5d6ffmeshtastic.kmp.jvm.androidTa5d6ff"Tb4b4b4)
Tb4b4b4}
Te6edf3kotlin Tb4b4b4{
Te6edf3jvmTb4b4b4(Tb4b4b4)
Te6edf3android Tb4b4b4{ T8b949e/* ... */ Tb4b4b4}
Te6edf3sourceSets Tb4b4b4{
Te6edf3commonMainTb4b4b4.Te6edf3dependencies Tb4b4b4{ T8b949e/* ... */ Tb4b4b4}
Te6edf3jvmMainTb4b4b4.Te6edf3dependencies Tb4b4b4{ T8b949e/* jvm-only additions */ Tb4b4b4}
Te6edf3androidMainTb4b4b4.Te6edf3dependencies Tb4b4b4{ T8b949e/* android-only additions */ Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Why: The convention uses Kotlin's hierarchy template API to create T383838jvmAndroidMain without the T383838Default Kotlin Hierarchy Template Not Applied Correctly warning triggered by hand-written T383838dependsOn(...) graphs.
Example: Creating a new KMP feature module
Current Pattern (GOOD β
):
Use T383838meshtastic.kmp.feature for any T383838feature:* module. It composes T383838kmp.library + T383838kmp.library.compose + T383838koin and provides all the common Compose/Lifecycle/Koin/Android dependencies that every feature needs:
T282828
Te6edf3plugins Tb4b4b4{
Te6edf3aliasTb4b4b4(Te6edf3libsTb4b4b4.Te6edf3pluginsTb4b4b4.Te6edf3meshtasticTb4b4b4.Te6edf3kmpTb4b4b4.Te6edf3featureTb4b4b4)
T8b949e// Optional: add only if this feature needs serialization
Te6edf3aliasTb4b4b4(Te6edf3libsTb4b4b4.Te6edf3pluginsTb4b4b4.Te6edf3meshtasticTb4b4b4.Te6edf3kotlinxTb4b4b4.Te6edf3serializationTb4b4b4)
Tb4b4b4}
Te6edf3kotlin Tb4b4b4{
Te6edf3jvmTb4b4b4(Tb4b4b4)
Te6edf3android Tb4b4b4{
Te6edf3namespace Tff7b72= Ta5d6ff"Ta5d6fforg.meshtastic.feature.yourfeatureTa5d6ff"
Te6edf3androidResourcesTb4b4b4.Te6edf3enable Tff7b72= Tff7b72false
Te6edf3withHostTest Tb4b4b4{ Te6edf3isIncludeAndroidResources Tff7b72= Tff7b72true Tb4b4b4}
Tb4b4b4}
Te6edf3sourceSets Tb4b4b4{
Te6edf3commonMainTb4b4b4.Te6edf3dependencies Tb4b4b4{
T8b949e// Only module-SPECIFIC deps here
Te6edf3implementationTb4b4b4(Te6edf3projectsTb4b4b4.Te6edf3coreTb4b4b4.Te6edf3commonTb4b4b4)
Te6edf3implementationTb4b4b4(Te6edf3projectsTb4b4b4.Te6edf3coreTb4b4b4.Te6edf3modelTb4b4b4)
Te6edf3implementationTb4b4b4(Te6edf3projectsTb4b4b4.Te6edf3coreTb4b4b4.Te6edf3uiTb4b4b4)
Tb4b4b4}
Te6edf3androidMainTb4b4b4.Te6edf3dependencies Tb4b4b4{
T8b949e// Only Android-specific extras here
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
What the plugin provides automatically:
β’ T383838commonMain: T383838compose-multiplatform-material3, T383838compose-multiplatform-materialIconsExtended, T383838jetbrains-lifecycle-viewmodel-compose, T383838koin-compose-viewmodel, T383838kermit
β’ T383838androidMain: T383838androidx-compose-bom (platform), T383838accompanist-permissions, T383838androidx-activity-compose, T383838androidx-compose-material3, T383838androidx-compose-material-iconsExtended, T383838androidx-compose-ui-text, T383838androidx-compose-ui-tooling-preview
β’ T383838commonTest: T383838core:testing
Why: Eliminates ~15 duplicate dependency declarations per feature module (modelled after Now in Android's T383838AndroidFeatureImplConventionPlugin).
Example: Adding Android-specific test config
Pattern: Add to T383838AndroidLibraryConventionPlugin.kt:
T282828
Te6edf3extensionsTb4b4b4.Te6edf3configureTff7b72<Te6edf3LibraryExtensionTff7b72> Tb4b4b4{
Te6edf3configureKotlinAndroidTb4b4b4(Tff7b72thisTb4b4b4)
Te6edf3testOptionsTb4b4b4.Te6edf3apply Tb4b4b4{
Te6edf3animationsDisabled Tff7b72= Tff7b72true
T8b949e// NEW: Android-specific test config
Te6edf3unitTestsTb4b4b4.Te6edf3isIncludeAndroidResources Tff7b72= Tff7b72true
Tb4b4b4}
Tb4b4b4}
Alternative: If it applies to both app and library, consider extracting a function:
T282828
Tff7b72internal Tff7b72fun Te6edf3ProjectTb4b4b4.Td2a8ffconfigureAndroidTestOptionsTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3extensionsTb4b4b4.Te6edf3configureTff7b72<Te6edf3CommonExtensionTff7b72> Tb4b4b4{
Te6edf3testOptionsTb4b4b4.Te6edf3apply Tb4b4b4{
Te6edf3animationsDisabled Tff7b72= Tff7b72true
T8b949e// Shared test options
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Duplication Heuristics
When to consolidate (DRY):
β’ β
Configuration appears in 3+ convention plugins
β’ β
The duplication changes together (same reasons to update)
β’ β
Extraction doesn't require complex type gymnastics
β’ β
Underlying Gradle extension is the same (T383838CommonExtension)
When to keep separate (Clarity):
β’ β
Different Gradle extension types (T383838ApplicationExtension vs T383838LibraryExtension)
β’ β
Plugin intent is explicit in T383838build.gradle.kts usage
β’ β
Duplication is small (<50 lines) and stable
β’ β
Future divergence between app/library handling is plausible
Examples in codebase:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ¬ββββββββββββββββ¬ββββββ
β Duplication β Status β Reβ¦ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββΌββββββ€
β T383838AndroidApplicationComposeConventionPlugin β T383838AndroidLibraryComposeConventiβ¦ β Kept Separate β Diβ¦ β
β T383838AndroidApplicationFlavorsConventionPlugin β T383838AndroidLibraryFlavorsConventiβ¦ β Kept Separate β Diβ¦ β
β T383838configureKmpTestDependencies() (7 modules) β Consolidated β Laβ¦ β
β T383838jvmAndroidMain hierarchy setup (4 modules) β Consolidated β Shβ¦ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ΄ββββββββββββββββ΄ββββββ
Testing Convention Changes
After modifying a convention plugin, verify:
T282828
T8b949e# 1. Code quality
./gradlew spotlessCheck detekt
T8b949e# 2. Compilation
./gradlew assembleDebug assembleRelease
T8b949e# 3. Tests
./gradlew Tffa657test T8b949e# All unit tests
./gradlew :feature:messaging:jvmTest T8b949e# Feature module tests
./gradlew :feature:node:testAndroidHostTest T8b949e# Android host tests
Documentation Requirements
When you add/modify a convention:
1. Add Kotlin docs to the function:
T282828
T8b949e/**
* Configure test dependencies for KMP modules.
*
* Automatically applies kotlin("test") to:
* - commonTest source set (all targets)
* - androidHostTest source set (Android-only)
*
* Usage: Called automatically by KmpLibraryConventionPlugin
*/
Tff7b72internal Tff7b72fun Te6edf3ProjectTb4b4b4.Td2a8ffconfigureKmpTestDependenciesTb4b4b4(Tb4b4b4) Tb4b4b4{ Tb4b4b4.Tb4b4b4.Tb4b4b4. Tb4b4b4}
2. Update AGENTS.md if convention affects developers
3. Update this guide if pattern changes
Performance Tips
β’ Configuration-time: Convention logic runs during Gradle configuration (0.5-2s)
β’ Build-time: No impact (conventions don't execute tasks)
β’ Optimization focus: Minimize T383838extensions.configure() blocks (lazy evaluation is preferred)
Good β
T282828
Te6edf3extensionsTb4b4b4.Te6edf3configureTff7b72<Te6edf3KotlinMultiplatformExtensionTff7b72> Tb4b4b4{
T8b949e// Single block for all source set configuration
Te6edf3sourceSetsTb4b4b4.Te6edf3apply Tb4b4b4{
Te6edf3commonTestTb4b4b4.Te6edf3dependencies Tb4b4b4{ T8b949e/* ... */ Tb4b4b4}
Te6edf3androidHostTestTff7b72?.Te6edf3dependencies Tb4b4b4{ T8b949e/* ... */ Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Avoid β
T282828
T8b949e// Multiple blocks - slower configuration
Te6edf3extensionsTb4b4b4.Te6edf3configureTff7b72<Te6edf3KotlinMultiplatformExtensionTff7b72> Tb4b4b4{
Te6edf3sourceSetsTb4b4b4.Te6edf3getByNameTb4b4b4(Ta5d6ff"Ta5d6ffcommonTestTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3dependencies Tb4b4b4{ T8b949e/* ... */ Tb4b4b4}
Tb4b4b4}
Te6edf3extensionsTb4b4b4.Te6edf3configureTff7b72<Te6edf3KotlinMultiplatformExtensionTff7b72> Tb4b4b4{
Te6edf3sourceSetsTb4b4b4.Te6edf3getByNameTb4b4b4(Ta5d6ff"Ta5d6ffandroidHostTestTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3dependencies Tb4b4b4{ T8b949e/* ... */ Tb4b4b4}
Tb4b4b4}
Common Pitfalls
β Mistake: Adding dependencies in the wrong place
T282828
T8b949e// WRONG: Adds to ALL modules, not just KMP
Te6edf3extensionsTb4b4b4.Te6edf3configureTff7b72<Te6edf3ProjectTff7b72> Tb4b4b4{
Te6edf3dependencies Tb4b4b4{ Te6edf3addTb4b4b4(Ta5d6ff"Ta5d6ffimplementationTa5d6ff"Tb4b4b4, Tb4b4b4.Tb4b4b4.Tb4b4b4.Tb4b4b4) Tb4b4b4} T8b949e// Global!
Tb4b4b4}
T8b949e// RIGHT: Scoped to specific source set/module type
Te6edf3commonTestTb4b4b4.Te6edf3dependencies Tb4b4b4{ Te6edf3implementationTb4b4b4(Tb4b4b4.Tb4b4b4.Tb4b4b4.Tb4b4b4) Tb4b4b4}
β Mistake: Extension type mismatch
T282828
T8b949e// WRONG: LibraryExtension isn't a subtype of ApplicationExtension
Te6edf3extensionsTb4b4b4.Te6edf3configureTff7b72<Te6edf3ApplicationExtensionTff7b72> Tb4b4b4{
T8b949e// Won't apply to library modules
Tb4b4b4}
T8b949e// RIGHT: Use CommonExtension or specific types
Te6edf3extensionsTb4b4b4.Te6edf3configureTff7b72<Te6edf3CommonExtensionTff7b72> Tb4b4b4{
T8b949e// Applies to both
Tb4b4b4}
β Mistake: Side effects during configuration
T282828
T8b949e// WRONG: Eager task configuration at plugin-apply time
Te6edf3tasksTb4b4b4.Te6edf3withTypeTff7b72<Te6edf3TestTff7b72> Tb4b4b4{
T8b949e// Can realize tasks too early
Tb4b4b4}
T8b949e// RIGHT: Lazy, configuration-cache-friendly wiring
Te6edf3tasksTb4b4b4.Te6edf3withTypeTff7b72<Te6edf3TestTff7b72>Tb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3configureEach Tb4b4b4{
T8b949e// Applies to existing and future tasks lazily
Tb4b4b4}
Related Files
β’ T383838AGENTS.md - Development guidelines (Section 3.B testing, Section 4.A build protocol)
β’ T383838build-logic/convention/build.gradle.kts - Convention plugin build config
Served by rngit 1.5.0 - Generated in 0.08s